<?php
/**
 * Copyright 2009 Joe Dotoff
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * HttpResponse class
 * A HTTP response
 *
 * @package     PDog
 * @subpackage  Http
 * @author      Joe D. <nonultimate@gmail.com>
 * @license     Apache License Version 2.0
 * @version     $Id: HttpResponse.php 33 2009-04-21 01:03:44Z nonultimate $
 */
class HttpResponse
{
    /**
     * Socket resource
     *
     * @var resource
     */
    protected $_sock;
    /**
     * HTTP headers for output
     *
     * @var array
     */
    protected $_headers;
    /**
     * HTTP state code and message
     *
     * @var array
     */
    protected $_state;
    /**
     * HTTP state code
     *
     * @var int
     */
    protected $_code;
    /**
     * Header flushing state
     *
     * @var bool
     */
    protected $_isFlushed;
    /**
     * Constructor
     */
    public function __construct()
    {
        $date = gmdate('D, j M Y H:i:s').' GMT';
        $signature = 'SimpleHttpd/0.1.0 PHP/'.PHP_VERSION;
        // Define default headers
        $this->_headers = array(
            'DATE'         => $date,
            'SERVER'       => $signature,
            'CONTENT-TYPE' => 'text/html'
        );
        // Define HTTP error state code and message
        $this->_state = array(
            '100' => 'Continue',
            '101' => 'Switch Protocols',
            '200' => 'OK',
            '201' => 'Created',
            '202' => 'Accepted',
            '203' => 'Non-Authoritative Information',
            '204' => 'No Content',
            '205' => 'Reset Content',
            '206' => 'Partial Content',
            '300' => 'Multiple Choices',
            '301' => 'Moved Permanently',
            '302' => 'Found',
            '303' => 'See Other',
            '304' => 'Not Modified',
            '305' => 'Use Proxy',
            '306' => 'Unused',
            '307' => 'Temporary Redirect',
            '400' => 'Bad Request',
            '401' => 'Unauthorized',
            '402' => 'Payment Required',
            '403' => 'Forbidden',
            '404' => 'Not Found',
            '405' => 'Method Not Allowed',
            '406' => 'Not Acceptable',
            '407' => 'Proxy Authentication Required',
            '408' => 'Request Timeout',
            '409' => 'Conflict',
            '410' => 'Gone',
            '411' => 'Length Required',
            '412' => 'Precondition Failed',
            '413' => 'Request Entity Too Large',
            '414' => 'Request-URI Too Long',
            '415' => 'Unsupported Media Type',
            '416' => 'Requested Range Not Satisfiable',
            '417' => 'Expectation Failed',
            '500' => 'Internal Server Error',
            '501' => 'Not Implemented',
            '502' => 'Bad Gateway',
            '503' => 'Service Unavailable',
            '504' => 'Gateway Timeout',
            '505' => 'HTTP Version Not Supported'
        );
        // Default state code
        $this->_code = 200;
        $this->_isFlushed = false;
    }
    /**
     * Close socket resource
     *
     * @return void
     */
    public function close()
    {
        if (is_resource($this->_sock)) {
            fclose($this->_sock);
        }
        $this->_sock = null;
    }
    /**
     * Set the file to output
     *
     * @param  string  $path  The script file to execute
     * @return void
     */
    public function execScript($path)
    {
        $str = '';
        try {
            $data = file_get_contents($path);
            $data = preg_replace('/[^<]*<\?(php)?/i', '', $data);
            $data = preg_replace('/\?>\s*$/', '', $data);
            ob_start();
            eval($data);
            $str = ob_get_clean();
        } catch (Exception $e) {
            echo "Exception: ".$e."\n";
        }
        $this->_headers['CONTENT-LENGTH'] = strlen($str);
        $this->write($str);
    }
    /**
     * Flush header to output
     *
     * @return void
     */
    protected function flush()
    {
        if (!is_resource($this->_sock)) {
            throw new Exception('The handler of HttpResponse not created');
        }
        fwrite($this->_sock, "HTTP/1.1 ".$this->getState()." ".$this->getMessage()."\r\n");
        foreach ($this->_headers as $key => $value) {
            $key = str_replace(' ', '-', ucwords(str_replace('-', ' ', strtolower($key))));
            fwrite($this->_sock, $key.": ".$value."\r\n");
        }
        fwrite($this->_sock, "Connection: Keep-Alive\r\n\r\n");
        $this->_isFlushed = true;
    }
    /**
     * Get header flushing state
     *
     * @return bool
     */
    public function isFlushed()
    {
        return $this->_isFlushed;
    }
    /**
     * Get content length of HTTP headers
     *
     * @return int
     */
    public function getContentLength()
    {
        return $this->_headers['CONTENT-LENGTH'];
    }
    /**
     * Get content type of HTTP headers
     *
     * @return void
     */
    public function getContentType()
    {
        return $this->_headers['CONTENT-TYPE'];
    }
    /**
     * Get HTTP response handler from the socket
     *
     * @param  resource  $sock  Socket connection resource
     * @return void
     */
    public function getHandler($sock)
    {
        if (!is_resource($sock)) {
            throw new Exception('Invalid socket resource for HttpResponse');
        }
        $this->_sock = $sock;
    }
    /**
     * Get HTTP state message
     *
     * @return string
     */
    public function getMessage()
    {
        $code = sprintf("%d", $this->_code);

        return $this->_state[$code];
    }
    /**
     * Get HTTP state code
     *
     * @return int
     */
    public function getState()
    {
        return $this->_code;
    }
    /**
     * Redirect to a URL
     *
     * @param  string  $url  The URL that will redirect to
     * @return void
     */
    public function redirect($url)
    {
        $this->setState(307);
        $this->_headers['LOCATION'] = $url;
        $this->flush();
    }
    /**
     * Set content length of HTTP headers
     *
     * @param  int  $length  The size of the data for output
     * @return void
     */
    public function setContentLength($length)
    {
        $this->_headers['CONTENT-LENGTH'] =  $length;
    }
    /**
     * Set content type of HTTP headers
     *
     * @param  string  $str  Content type
     * @return void
     */
    public function setContentType($str)
    {
        $this->_headers['CONTENT-TYPE'] = $str;
    }
    /**
     * Set HTTP header
     *
     * @param  string $name   The header name
     * @param  mixed  $value  The header value
     * @return void
     */
    public function setHeader($name, $value)
    {
        $name = strtoupper($name);
        $this->_headers[$name] = $value;
    }
    /**
     * Set the file to output
     *
     * @param  string  $path  The requested file path
     * @return void
     */
    public function setOutputFile($path)
    {
        $size = filesize($path);
        $last_modified = gmdate('D, j M Y H:i:s', filemtime($path)).' GMT';
        $this->_headers['CONTENT-LENGTH'] = $size;
        $this->_headers['LAST-MODIFIED']  = $last_modified;
        if ($size > 0) {
            $ext = '';
            $name = basename($path);
            $offset = strrpos($name, '.');
            if (false !== $offset) {
                $ext = strtolower(substr($name, $offset + 1));
            }
            if ('php' === $ext) {
                $this->execScript($path);
            } else {
                $fp = fopen($path, 'r');
                while (!feof($fp)) {
                    $this->write(fread($fp, 1024));
                }
                fclose($fp);
            }
        }
    }
    /**
     * Set HTTP state code
     *
     * @param  int  $code  HTTP state code
     * @return void
     */
    public function setState($code)
    {
         $this->_code = $code;
    }
    /**
     * Write date for output
     *
     * @param  string  $data
     */
    public function write($data)
    {
        if (!is_resource($this->_sock)) {
            throw new Exception('The handler of HttpResponse not created');
        }
        if (!$this->isFlushed()) {
            $this->flush();
        }
        fwrite($this->_sock, $data);
    }
}
?>